package com.atom.module.lib.opengles.util

import android.content.res.Resources
import android.opengl.GLES30
import android.opengl.GLU
import android.util.Log
import androidx.annotation.NonNull
import androidx.annotation.RawRes
import com.atom.module.lib.opengles.ext.ReturnWrapper
import com.atom.module.lib.opengles.exception.ShaderException
import com.blankj.utilcode.util.LogUtils
import java.lang.RuntimeException

/**
 * All rights Reserved, Designed By www.rongdasoft.com
 * @version V1.0
 * @Title: ShaderUtils
 * @Description:
 * @author: wuyongzheng
 * @date: 2022/1/18
 * @Copyright: 2022/1/18 www.rongdasoft.com Inc. All rights reserved.
 */
object ShaderUtils {

    val DEBUG = true


    fun loadShaderFromAssets(@NonNull resources: Resources, assetsFileName: String): String {
        return StreamUtils.readInputStream(resources.assets.open(assetsFileName))
    }

    fun loadShaderFromRes(@NonNull resources: Resources, @RawRes assetsFileName: Int): String {
        return StreamUtils.readInputStream(resources.openRawResource(assetsFileName))
    }

    fun createProgram(
        vertexShaderIndex: Int,
        fragmentShaderIndex: Int,
        otherShaderIndex: Int = -1,
        attribBindings: Map<String, Int>? = null
    ): Int {
        val program = GLES30.glCreateProgram()
        GLES30.glAttachShader(program, vertexShaderIndex)
        checkGLError(
            String.format(
                "AttachShader {program[%s] ,vertexShader[%s]} ",
                program,
                vertexShaderIndex
            )
        )
        GLES30.glAttachShader(program, fragmentShaderIndex)
        checkGLError(
            String.format(
                "AttachShader {program[%s] ,fragmentShader[%s]} ",
                program,
                vertexShaderIndex
            )
        )
        if (otherShaderIndex != -1) {
            GLES30.glAttachShader(program, otherShaderIndex)
            checkGLError(
                String.format(
                    "AttachShader {program[%s] ,otherShader[%s]} ",
                    program,
                    vertexShaderIndex
                )
            )
        }
        attribBindings?.forEach { entry ->
            GLES30.glBindAttribLocation(program, entry.value, entry.key)
        }
        GLES30.glLinkProgram(program)
        val status = IntArray(1)
        GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, status, 0)
        if (status[0] != GLES30.GL_TRUE) {
            val msg = GLES30.glGetProgramInfoLog(program) ?: "null"
            deleteProgram(program)
            throw ShaderException(
                "GpuProgram  ${this.javaClass.simpleName}", "buildProgram",
                "Error linking GL program \n$msg"
            )
        }
        return program
    }


    fun createVertexShader(shaderSourceCode: String): Int {
        return createShader(GLES30.GL_VERTEX_SHADER, shaderSourceCode)
    }

    fun createFragmentShader(shaderSourceCode: String): Int {
        return createShader(GLES30.GL_FRAGMENT_SHADER, shaderSourceCode)
    }

    fun createShader(shaderType: Int, shaderSourceCode: String): Int {
        val shader = GLES30.glCreateShader(shaderType)
        if (shader <= 0) {
            throw ShaderException(
                "ShaderUtil",
                "createShader",
                shaderTypeToString(shaderType) + "-- glCreateShader -- " + shaderSourceCode
            )
        }
        GLES30.glShaderSource(shader, shaderSourceCode)
        GLES30.glCompileShader(shader)
        val status = IntArray(1)
        GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, status, 0)
        if (status[0] != GLES30.GL_TRUE) {
            val log = GLES30.glGetShaderInfoLog(shader) ?: "null"
            GLES30.glDeleteShader(shader)
            throw ShaderException(
                "ShaderUtil",
                "createShader",
                shaderTypeToString(shaderType) + "-- glGetShaderiv -- " + log
            )
        }
        return shader
    }

    /**
     * shader type transform String value to print
     */
    fun shaderTypeToString(shaderType: Int): String {
        return when (shaderType) {
            GLES30.GL_VERTEX_SHADER -> "GL_VERTEX_SHADER"
            GLES30.GL_FRAGMENT_SHADER -> "GL_FRAGMENT_SHADER"
            else -> "GL_ERROR_SHADER"
        }
    }


    fun deleteProgram(programId: Int) {
        GLES30.glDeleteProgram(programId)
    }

    fun deleteShader(shaderId: Int) {
        GLES30.glDeleteShader(shaderId)
    }


    fun checkGLError(err: String) {
        if (!DEBUG) return
        var error: Int // error 变量
        while (GLES30.glGetError().also { error = it } != GLES30.GL_NO_ERROR) {
            //后台打印错误
            throw RuntimeException("$err: glError $error  ${GLU.gluErrorString(error)}")
        }
    }

    fun checkGlProgramLocation(location: Int, label: String) {
        if (!DEBUG) return
        if (location < 0) {
            val message = "Unable to locate $label in program"
            throw RuntimeException(message)
        }
    }

}